# -*- coding: utf-8 -*-
#
#    Copyright (C) 2012 Gonzalo Bonigo <gonzalobonigo@gmail.com>
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see http://www.gnu.org/licenses/
#


"""
    En este modulo se implementan varios metedos de sincronizacion de sistemas.
"""

__version__ = 3.1

import random

from functions import *
import lts

#TODO: Put this into an enumeration.
FIRST_CALL  = 1
SECOND_CALL = 2
THIRD_CALL  = 3

def _sync_tow_random (system, sync_set, verbose=False):
    if len(system) > 1:
        s1 = system.pop(int((random.random()*10) % len(system)))
        s2 = system.pop(int((random.random()*10) % len(system)))
        system = system + [s1.sync(s2,sync_set)]
        if verbose:
            print "Synchronizing:",s1.name,"||",s2.name
    return system

def _sync_pairs_random (system, sync_set, verbose=False):
    new_system = []
    while len(system) > 1:
        s1 = system.pop(int((random.random()*10) % len(system)))
        s2 = system.pop(int((random.random()*10) % len(system)))
        new_system += [s1.sync(s2, sync_set)]
        if verbose:
            print "Synchronizing:",s1.name,"||",s2.name
    new_system += system
    return new_system

def _sync_diag_pairs (system, sync_set, verbose=False):
    # Hago una copia de system.
    old_system = list(system)
    #  Lista de subsistemas diagnosticables.
    diag_subsystems = [s for s in system if s.isdiag]
    # Lista de subsystemas no diagnosticables.
    nondiag_subsystems = [s for s in system if not s.isdiag]
    # Sincronizo de a paraes en cada lista.
    new_system = _sync_pairs_random(diag_subsystems, sync_set, verbose)
    new_system += _sync_pairs_random(nondiag_subsystems, sync_set, verbose)
    if set(old_system) == set(new_system):
        return _sync_pairs_random (system, sync_set, verbose)
    else:
        return new_system
    
def _sync_no_sharing (system, sync_set, verbose=False, call=FIRST_CALL):
    # Hacemos una copia de system.
    old_system = list(system)
    # Sistema resultante.
    new_system = []
    # Clasifico los subsistemas que no comparten eventos de sincronizacion.
    subsystem_classes = [[]]
    while system:
        s1 = system.pop()
        for xs in subsystem_classes:
            if len(xs) == 0:
                xs.append(s1)
                subsystem_classes.append([])
                break
            share_event = False
            for s2 in xs:
                if share_events(s1,s2,sync_set):
                    share_event = True
                    break
            if not share_event:
                xs.append(s1)
                break
    # Sicronizo de a pares los subsistemas de cada clase.
    subsystem_classes.remove([])
    for xs in subsystem_classes:
        new_system += _sync_pairs_random(xs, sync_set, verbose)
    # Si el nuevo sistema es igual al anterior es porque no hubo sincronizacion.
    if set(old_system) == set(new_system):
        # Si estoy en modo verboso informo.
        if verbose:
            print "The heuristic 3 couldn't make any composition."
            print "Changing to heuristric 4.\n"
        # Intento con otra heuristica de sincronizacion.
        if call == FIRST_CALL:
            return _sync_partial_sharing(old_system, sync_set, verbose, SECOND_CALL)
        elif call == SECOND_CALL:
            return _sync_full_sharing(old_system, sync_set, verbose, THIRD_CALL)
        else:
            raise LTSError("Third call to function _sync_no_sharing().")
    else:
        # Si no son iguales devuelvo el nuevo sistema.
        return new_system

def _sync_full_sharing (system, sync_set, verbose=False, call=FIRST_CALL):
    # Hacemos una copia de system.
    old_system = list(system)
    # Sistema resultante.
    new_system = []
    # Clasifico los subsistemas que no comparten eventos de sincronizacion.
    subsystem_classes = [[]]
    while system:
        s1 = system.pop()
        for xs in subsystem_classes:
            if len(xs) == 0:
                xs.append(s1)
                subsystem_classes.append([])
                break
            share_all_event = True
            for s2 in xs:
                if not share_all_events(s1,s2,sync_set):
                    share_all_event = False
                    break
            if share_all_event:
                xs.append(s1)
                break
    # Sicronizo de a pares los subsistemas de cada clase.
    subsystem_classes.remove([])
    for xs in subsystem_classes:
        new_system += _sync_pairs_random(xs, sync_set, verbose)
    # Si el nuevo sistema es igual al anterior es porque no hubo sincronizacion.
    if set(old_system) == set(new_system):
        # Si estoy en modo verboso informo.
        if verbose:
            print "The heuristic 5 couldn't make any composition."
            print "Changing to heuristric 4.\n"
        # Intento con otra heuristica de sincronizacion.
        if call == FIRST_CALL:
            return _sync_partial_sharing(old_system, sync_set, verbose, SECOND_CALL)
        elif call == SECOND_CALL:
            return _sync_no_sharing(old_system, sync_set, verbose, THIRD_CALL)
        else:
            raise LTSError("Third call to function _sync_full_sharing().")
    else:
        # Si no son iguales devuelvo el nuevo sistema.
        return new_system

def _sync_partial_sharing (system, sync_set, verbose=False, call=FIRST_CALL):
    # Hacemos una copia de system.
    old_system = list(system)
    # Sistema resultante.
    new_system = []
    # Clasifico los subsistemas que no comparten eventos de sincronizacion.
    subsystem_classes = [[]]
    while system:
        s1 = system.pop()
        for xs in subsystem_classes:
            if len(xs) == 0:
                xs.append(s1)
                subsystem_classes.append([])
                break
            share_event = True
            for s2 in xs:
                if not share_events(s1,s2,sync_set):
                    share_event = False
                    break
            if share_event:
                xs.append(s1)
                break
    # Sicronizo de a pares los subsistemas de cada clase.
    subsystem_classes.remove([])
    for xs in subsystem_classes:
        new_system += _sync_pairs_random(xs, sync_set, verbose)
    # Si el nuevo sistema es igual al anterior es porque no hubo sincronizacion.
    if set(old_system) == set(new_system):
        # Si estoy en modo verboso informo.
        if verbose:
            print "The heuristic 4 couldn't make any composition."
            print "Changing to heuristric 3.\n"
        # Intento con otra heuristica de sincronizacion.
        if call == FIRST_CALL:
            return _sync_full_sharing(old_system, sync_set, verbose, SECOND_CALL)
        elif call == SECOND_CALL:
            return _sync_no_sharing(old_system, sync_set, verbose, THIRD_CALL)
        else:
            raise LTSError("Third call to function _sync_partial_sharing().")
    else:
        # Si no son iguales devuelvo el nuevo sistema.
        return new_system

def _sync_sequential (system, sync_set, verbose=False):
    # Compongo los subsistemas de manera secuencial.
    if verbose:
        print "Synchronizing:", \
              ('||').join([s.name.replace('.aut','') for s in system])
    s1 = system.pop(0)
    while system:
        s2 = system.pop(0)
        s1 = s1.sync(s2, sync_set)
    return [s1]


# TODO: No magic numbers; use an enumeration.
heuristics = dict()
heuristics[0] = _sync_tow_random
heuristics[1] = _sync_pairs_random
heuristics[2] = _sync_diag_pairs
heuristics[3] = _sync_no_sharing
heuristics[4] = _sync_partial_sharing
heuristics[5] = _sync_full_sharing
heuristics[6] = _sync_sequential

def synchronize (system, sync_set, level=0, verbose=False):
    """
        Descripcion:
            Toma un conjunto de subsistemas y realiza composisciones sobre estos
            segun el nivel de sincronizacion especificado.
        Argumentos:
            system   -  Instancia de la clase Set() que contiene el conjunto de
                        subsistemas a componer. Cada subsistema debe ser una
                        instancia de la clase lts.LTS().
            sync_set -  Instancia de la clase Set() que contiene el conjunto de
                        eventos en los cuales se quiere sincronizar.
            level    -  Es un numero entre 0 y 6 que indica el nivel (tipo) de
                        composicion a utilizar. Por defecto su valor es 0.
            verbose  -  Es un booleano que dice si se debe ejecutar la funcion
                        en modo verboso o no.
        Retorno:
            Instancia de la clase Set() que contiene el nuevo conjunto de
            subsistemas (instancias de la clase lts.LTS()) luego de realizar la
            o las composiciones necesarias.
    """
    if verbose:
        print ""
    return heuristics[level](system, sync_set, verbose)
